package com.t360.cubesolver;

import java.util.ArrayList;

/**
 * Object that models a piece of the puzzle
 * @author fmanganiello
 */
public class PuzzlePiece implements Cloneable {
	private String[] lines;
	private int pieceNumber;
	private int rotation;
	private int flip;
	
	/**
	 * Each piece is characterized by:
	 * @param lines Its "shape" modeled as array of strings ("o"'s and " "'s)
	 * @param rotation Its current rotation status (each piece can be rotated up to 4 times)
	 * @param flip Its current flip status (each piece, for each rotation, can be flip)
	 * @param pieceNumber Its unique identifier
	 */
	public PuzzlePiece(String[] lines, int rotation, int flip, int pieceNumber) {
		this.lines = new String[lines.length];
		for (int i=0; i < lines.length; ++i) {
			this.lines[i] = new String(lines[i]);
		}
		
		this.rotation = rotation;
		this.flip = flip;
		this.pieceNumber = pieceNumber;
	}
	
	public PuzzlePiece(String lines, int rotation, int flip, int pieceNumber) {
		ArrayList<String> tmpLines = new ArrayList<String>();
		
		for (String line : lines.split("\n")) {
			if (!line.trim().isEmpty()) {
				tmpLines.add(line);
			}
		}
		
		this.lines = tmpLines.toArray(new String[tmpLines.size()]);
		this.rotation = rotation;
		this.flip = flip;
		this.pieceNumber = pieceNumber;
	}
	
	/**
	 * It returns true if the right side of the piece matches the left side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesRightOnLeftOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer rightLineBuff = new StringBuffer();
		StringBuffer leftLineBuff = new StringBuffer();
		
		for (int i=0; i < lines.length; ++i) {
			rightLineBuff.append(lines[i].charAt(lines[i].length()-1));
			leftLineBuff.append(piece.lines[i].charAt(0));
		}
		
		String rightLine = rightLineBuff.toString();
		String leftLine = leftLineBuff.toString();
		
		for (int i=0; i < rightLine.length(); ++i) {
			if (i == 0 || i == rightLine.length()-1) {
				if (rightLine.charAt(i) != ' ' && leftLine.charAt(i) != ' ') {
					return false;
				}
			}
			else if (rightLine.charAt(i) == leftLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the left side of the piece matches the left side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesLeftOnLeftOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer myLeftLineBuff = new StringBuffer();
		StringBuffer otherLeftLineBuff = new StringBuffer();
		
		for (int i=0; i < lines.length; ++i) {
			myLeftLineBuff.append(lines[i].charAt(0));
			otherLeftLineBuff.append(piece.lines[i].charAt(0));
		}
		
		String myLeftLine = myLeftLineBuff.toString();
		String otherLeftLine = otherLeftLineBuff.toString();
		
		for (int i=0; i < otherLeftLine.length(); ++i) {
			if (otherLeftLine.charAt(i) == myLeftLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the right side of the piece matches the right side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesRightOnRightOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer myRightLineBuff = new StringBuffer();
		StringBuffer otherRightLineBuff = new StringBuffer();
		
		for (int i=0; i < lines.length; ++i) {
			myRightLineBuff.append(lines[i].charAt(lines[i].length()-1));
			otherRightLineBuff.append(piece.lines[i].charAt(lines[i].length()-1));
		}
		
		String myRightLine = myRightLineBuff.toString();
		String otherRightLine = otherRightLineBuff.toString();
		
		for (int i=0; i < myRightLine.length(); ++i) {
			if (myRightLine.charAt(i) == otherRightLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the bottom side of the piece matches the left side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesBottomOnLeftOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer leftLineBuff = new StringBuffer();
		String bottomLine = lines[lines.length-1];
		
		for (int i=0; i < lines.length; ++i) {
			leftLineBuff.append(piece.lines[i].charAt(0));
		}
		
		String leftLine = leftLineBuff.toString();
		
		for (int i=0; i < leftLine.length(); ++i) {
			if (i == 0 || i == leftLine.length()-1) {
				if (leftLine.charAt(i) != ' ' && bottomLine.charAt(i) != ' ') {
					return false;
				}
			}
			else if (leftLine.charAt(i) == bottomLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the bottom side of the piece matches the right side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesBottomOnRightOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer rightLineBuff = new StringBuffer();
		String bottomLine = lines[lines.length-1];
		
		for (int i=0; i < lines.length; ++i) {
			rightLineBuff.append(piece.lines[i].charAt(lines[i].length()-1));
		}
		
		String rightLine = rightLineBuff.toString();
		
		for (int i=0; i < rightLine.length(); ++i) {
			if (i == 0 || i == rightLine.length()-1) {
				if (rightLine.charAt(i) != ' ' && bottomLine.charAt(i) != ' ') {
					return false;
				}
			}
			else if (rightLine.charAt(i) == bottomLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the top side of the piece matches the left side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesTopOnLeftOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer leftLineBuff = new StringBuffer();
		String topLine = lines[0];
		
		for (int i=0; i < lines.length; ++i) {
			leftLineBuff.append(piece.lines[i].charAt(0));
		}
		
		String leftLine = leftLineBuff.toString();
		
		for (int i=0; i < leftLine.length(); ++i) {
			if (i == 0 || i == leftLine.length()-1) {
				if (leftLine.charAt(i) != ' ' && topLine.charAt(i) != ' ') {
					return false;
				}
			}
			else if (leftLine.charAt(i) == topLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the top side of the piece matches the right side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesTopOnRightOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		StringBuffer rightLineBuff = new StringBuffer();
		String topLine = lines[0];
		
		for (int i=0; i < lines.length; ++i) {
			rightLineBuff.append(piece.lines[i].charAt(lines[i].length()-1));
		}
		
		String rightLine = rightLineBuff.toString();
		
		for (int i=0; i < rightLine.length(); ++i) {
			if (i == 0 || i == rightLine.length()-1) {
				if (rightLine.charAt(i) != ' ' && topLine.charAt(i) != ' ') {
					return false;
				}
			}
			else if (rightLine.charAt(i) == topLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns true if the bottom side of the piece matches the top side of the piece "piece"
	 * @param piece
	 * @return
	 */
	public boolean matchesBottomOnTopOfPiece(PuzzlePiece piece) {
		if (piece == null) {
			return false;
		}
		
		String bottomLine = lines[lines.length-1];
		String topLine = piece.lines[0];
		
		for (int i=0; i < topLine.length(); ++i) {
			if (topLine.charAt(i) == bottomLine.charAt(i)) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * It returns a string that contains <n> pieces printed aligned, one on the right of the other.
	 * Utility method used for printing the top part of the solution
	 * @param pieces
	 * @return
	 */
	public String align(PuzzlePiece... pieces) {
		StringBuffer out = new StringBuffer();
		
		for (int i=0; i < lines.length; ++i) {
			out.append(lines[i]);
			
			for (int j=0; j < pieces.length; ++j) {
				if (pieces[j] != null) {
					out.append(pieces[j].lines[i]);
				}
			}
			
			out.append("\n");
		}
		
		return out.toString();
	}
	
	/**
	 * It rotates the piece by 90 degrees on its left
	 */
	public void rotateLeft() {
		char[][] matrix = new char[lines.length][lines.length];
		
		for (int i=0; i < this.lines.length; ++i) {
			matrix[i] = new char[this.lines.length];
			for (int j=0; j < this.lines.length; ++j) {
				matrix[i][j] = this.lines[j].charAt(this.lines.length-i-1);
			}
		}
		
		for (int i=0; i < lines.length; ++i) {
			lines[i] = new String(matrix[i]);
		}
		
		rotation = (rotation+1)%4;
	}
	
	/**
	 * It flips the piece
	 */
	public void flip() {
		for (int i=0; i < lines.length; ++i) {
			lines[i] = new StringBuffer(lines[i]).reverse().toString();
		}
		
		flip = (flip+1)%2;
	}
	
	@Override
	public String toString() {
		StringBuffer out = new StringBuffer();
		for (String line : lines) {
			out.append(line + "\n");
		}
		
		out.append("\n");
		return out.toString();
	}
	
	/**
	 * It returns the piece as string "indented" by as many spaces as its width,
	 * used in order to nicely print the solution (the bottom pieces)
	 * @return
	 */
	public String toIndentedString() {
		StringBuffer out = new StringBuffer();
		
		for (String line : lines) {
			for (int i=0; i < line.length(); ++i) {
				out.append(' ');
			}
			
			out.append(line + "\n");
		}
		
		out.append("\n");
		return out.toString();
	}
	
	/**
	 * @return The rotation status of the piece, included in the range [0,3]
	 */
	public int getRotation() {
		return rotation;
	}
	
	@Override
	public PuzzlePiece clone() {
		return new PuzzlePiece(lines, rotation, flip, pieceNumber);
	}
	
	/**
	 * @return The unique identifier of the piece
	 */
	public int index() {
		return pieceNumber;
	}
}
